package top.cardone.template.support.impl;

import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.Setter;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.util.CollectionUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.TableUtils;
import top.cardone.core.CodeException;
import top.cardone.template.support.TemplateSupport;

import java.io.File;
import java.util.Map;
import java.util.Objects;

/**
 * @author yao hai tao
 */
@lombok.extern.log4j.Log4j2
public class TemplateSupportImpl implements TemplateSupport {
    /**
     * 消息格式化映射
     */
    @lombok.Getter
    @lombok.Setter
    private Map<String, Configuration> targetConfigurations = Maps.newConcurrentMap();

    /**
     * freemarker.template.Configuratio
     */
    @lombok.Getter
    @lombok.Setter
    private Configuration defaultTargetConfiguration;

    /**
     * 显示model
     */
    @lombok.Getter
    @lombok.Setter
    private Boolean showModel = true;

    /**
     * 显示解析模板后字符串
     */
    @lombok.Getter
    @lombok.Setter
    private Boolean showProcessTemplateIntoString = true;

    /**
     * 显示模板字符串
     */
    @lombok.Getter
    @lombok.Setter
    private Boolean showTemplateString = false;

    @Setter
    private Long countUpperLimit = 1000L;

    /**
     * 模板映射
     */
    private final Map<String, Template> templateMap = Maps.newConcurrentMap();

    @Override
    public String process(final String filePath, final Object model) {
        return this.process(null, filePath, model);
    }

    @Override
    public String toString(String configurationKey, String filePath) {
        Configuration configuration = getConfiguration(configurationKey);

        if (configuration == null) {
            return StringUtils.EMPTY;
        }

        try {
            Object object = configuration.getTemplateLoader().findTemplateSource(filePath);

            if (object == null) {
                throw new CodeException("file don't exist", "文件不存在");
            }

            File file = new File(object.toString());

            if (!file.exists()) {
                throw new CodeException("file don't exist", "文件不存在");
            }

            return FileUtils.readFileToString(file, Charsets.UTF_8);
        } catch (java.io.IOException e) {
            throw new CodeException("system error", e);
        }
    }

    @Override
    public String toString(String filePath) {
        return this.toString(null, filePath);
    }

    private Configuration getConfiguration(String key) {
        if (StringUtils.isBlank(key)) {
            return defaultTargetConfiguration;
        }

        if (CollectionUtils.isEmpty(targetConfigurations)) {
            return null;
        }

        if (targetConfigurations.containsKey(key)) {
            return targetConfigurations.get(key);
        }

        return defaultTargetConfiguration;
    }

    @Override
    public String process(String configurationKey, String filePath, Object model) {
        log(configurationKey, filePath);

        Configuration configuration = getConfiguration(configurationKey);

        if (configuration == null) {
            return StringUtils.EMPTY;
        }

        try {
            final Template template = configuration.getTemplate(filePath);

            if (BooleanUtils.isTrue(this.showTemplateString)) {
                if (log.isWarnEnabled()) {
                    log.warn(template.toString());
                }
            }

            String str;

            try (java.io.StringWriter writer = new java.io.StringWriter()) {
                template.process(model, writer);

                str = writer.toString();
            }

            if (BooleanUtils.isTrue(this.showProcessTemplateIntoString)) {
                if (log.isWarnEnabled()) {
                    log.warn(str);
                }
            }

            if (BooleanUtils.isTrue(this.showModel)) {
                if (log.isWarnEnabled()) {
                    log.warn(Objects.toString(model));
                }
            }

            return StringUtils.defaultIfBlank(str, StringUtils.EMPTY);
        } catch (freemarker.template.TemplateException | java.io.IOException e) {
            throw new CodeException("system error", e);
        }
    }

    @Setter
    private String taskExecutorBeanName = "slowTaskExecutor";

    private void log(String configurationKey, String filePath) {
        ApplicationContextHolder.getBean(AsyncListenableTaskExecutor.class, this.taskExecutorBeanName).submitListenable(() -> {
            if (countUpperLimit > 0L) {
                String rowKey = this.getClass().getName();

                String columnKey = StringUtils.join(configurationKey, ", ", filePath);

                Long count = TableUtils.longAdderIncrementGetSum(rowKey, columnKey);

                if (count % countUpperLimit == 0) {
                    log.error(StringUtils.join("调用较频繁, rowKey: ", rowKey, ", columnKey: ", columnKey, ", count: ", count));
                }
            }
        });
    }

    @Override
    public String processString(final String templateString, final Object model) {
        return processString(null, templateString, model);
    }

    @Override
    public String processString(String configurationKey, String templateString, Object model) {
        Configuration configuration = getConfiguration(configurationKey);

        if (configuration == null) {
            return StringUtils.EMPTY;
        }

        if (StringUtils.isBlank(templateString)) {
            return StringUtils.EMPTY;
        }

        if (BooleanUtils.isTrue(this.showTemplateString)) {
            if (log.isWarnEnabled()) {
                log.warn(templateString);
            }
        }

        try (java.io.StringWriter writer = new java.io.StringWriter()) {
            final String name = DigestUtils.md5Hex(templateString);

            Template template = templateMap.get(name);

            if (template == null) {
                try (java.io.Reader reader = new java.io.StringReader(templateString)) {
                    template = new Template(name, reader, configuration);

                    templateMap.put(name, template);
                }
            }

            template.process(model, writer);

            String str = writer.toString();

            if (BooleanUtils.isTrue(this.showProcessTemplateIntoString)) {
                if (log.isWarnEnabled()) {
                    log.warn(str);
                }
            }

            if (BooleanUtils.isTrue(this.showModel)) {
                if (log.isWarnEnabled()) {
                    log.warn(Objects.toString(model));
                }
            }

            return str;
        } catch (Exception e) {
            throw new CodeException("system error", e);
        }
    }
}